<?php

namespace chick1993\util;


class DbMap
{
    private $alias;
    private $param;
    private $map = [];

    protected function __construct(array $param, string $alias = '')
    {
        $this->param = $param;
        $this->alias = $alias;
    }

    static public function op2Func(string $op): array
    {
        $param1 = [
            'gt' => ['gt', 'gte', '>', '大于', '大于等于'],
            'lt' => ['lt', 'lte', '<', '小于', '小于等于'],
            'eq' => ['eq', '=', '等于', '不等于'],
        ];

        $param2 = ['gte', 'lte', '大于等于', '小于等于', '不等于'];

        $return = [];

        foreach ($param1 as $k => $p) {
            if (in_array($op, $p)) {
                $return[] = $k;
                if (in_array($op, $param2)) {
                    $return[] = true;
                }
                break;
            }
        }
        return $return;
    }

    /**
     * @param array $param 请求参数 [name1=>value1,name2=>value2...]
     * @param string $alias 全局别名 优先级低于$field数组设置
     * @return static
     */
    static public function init(array $param, string $alias = ''): self
    {
        return new static($param, $alias);
    }

    /**
     * 批量查询 (自动识别区分like或in查询)
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param ?string $separator 分隔符，默认自动识别常用分隔符
     * @return $this
     */
    public function batch($field, bool $not = false, string $separator = null): self
    {
        $op = $not === true ? 'not in' : 'in';
        $replace = !empty($separator) ? [$separator] : [];
        return $this->_set($field, $op, function ($val, &$new_op) use ($not, $replace) {
            $val = $this->batchStrToArr($val, $replace);
            if (count($val) == 1) {
                $new_op = $not === true ? 'not like' : 'like';
                $val = "%{$val[0]}%";
            }
            return $val;
        });
    }

    /**
     * [not] between 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param string $separator 分隔符
     * @return $this
     */
    public function between($field, bool $not = false, string $separator = ','): self
    {
        return $this->_setBetween($field, $not, $separator);
    }

    /**
     * [not] between 按日期时间范围查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param string $separator 分隔符
     * @param string $format 时间格式，默认Y-m-d H:i:s
     * @return $this
     */
    public function dateBetween($field, bool $not = false, string $separator = ',', string $format = 'Y-m-d H:i:s'): self
    {
        $this->_setBetween($field, $not, $separator, function ($val) use ($format, $separator) {
            return Time::toRangeDate($val, $format, $separator);
        });
        return $this;
    }

    /**
     * [not] like 查询，以关键词结尾
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function endLike($field, bool $not = false): self
    {
        $op = $not === true ? 'not like' : 'like';
        return $this->_set($field, $op, function ($val) {
            return sprintf('%%%s', $val);
        });
    }

    /**
     * [not] eq 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function eq($field, bool $not = false): self
    {
        $op = $not === true ? '<>' : '=';
        return $this->_set($field, $op);
    }

    /**
     * [not] eq 查询并将字段转为日期
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function eqDate($field, bool $not = false, string $format = 'Y-m-d H:i:s'): self
    {
        $op = $not === true ? '<>' : '=';
        return $this->_set($field, $op, function ($val) use ($format) {
            return Time::toDate($val, $format);
        });
    }

    /**
     * [not] eq 查询并将字段转为时间戳
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function eqStamp($field, bool $not = false): self
    {
        $op = $not === true ? '<>' : '=';
        return $this->_set($field, $op, function ($val) {
            return Time::toStamp($val);
        });
    }

    /**
     * 大于[等于] 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $eq 是否等于
     * @return $this
     */
    public function gt($field, bool $eq = false): self
    {
        $op = $eq === true ? '>=' : '>';
        return $this->_set($field, $op);
    }

    /**
     * [not] in 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param ?string $separator 分隔符，默认自动识别常用分隔符
     * @return $this
     */
    public function in($field, bool $not = false, string $separator = null): self
    {
        $op = $not === true ? 'not in' : 'in';
        $replace = !empty($separator) ? [$separator] : [];
        return $this->_set($field, $op, function ($val, &$new_op) use ($not, $replace) {
            $val = $this->batchStrToArr($val, $replace);
            if (count($val) == 1) {
                $new_op = $not === true ? '<>' : '=';
                $val = $val[0];
            }
            return $val;
        });
    }

    /**
     * [not] like 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function like($field, bool $not = false): self
    {
        $op = $not === true ? 'not like' : 'like';
        return $this->_set($field, $op, function ($val) {
            return sprintf('%%%s%%', $val);
        });
    }

    /**
     * 小于[等于] 查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $eq 是否等于
     * @return $this
     */
    public function lt($field, bool $eq = false): self
    {
        $op = $eq === true ? '<=' : '<';
        return $this->_set($field, $op);
    }

    /**
     * is [not] null 空值查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function isNull($field, bool $not = false): self
    {
        $op = $not === true ? 'not null' : 'null';
        return $this->_set($field, $op, function ($v) {
            return null;
        });
    }

    /**
     * [not] between 按时间戳范围查询
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param string $separator 分隔符
     * @return $this
     */
    public function stampBetween($field, bool $not = false, string $separator = ','): self
    {
        $this->_setBetween($field, $not, $separator, function ($val) use ($separator) {
            return Time::toRangeStamp($val, $separator);
        });
        return $this;
    }

    /**
     * [not] like 查询，以关键词开头
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @return $this
     */
    public function startLike($field, bool $not = false): self
    {
        $op = $not === true ? 'not like' : 'like';
        return $this->_set($field, $op, function ($val) {
            return sprintf('%s%%', $val);
        });
    }

    /**
     * 转换查询数组
     * @return array
     */
    public function toArray(): array
    {
        return $this->map;
    }

    /**
     * 获取原始查询数据（指定字段）
     * @param $field
     * @return mixed
     */
    public function getRawValue($field)
    {
        return $this->param[$field] ?? null;
    }

    /**
     * 获取原始查询数据（全部）
     * @return array
     */
    public function getRawValues(): array
    {
        return $this->param;
    }

    /**
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @return array
     */
    protected function _getField($field): array
    {
        $data = [];
        if (!is_array($field)) $field = explode(',', $field);
        foreach ($field as $k => $item) {
            if (is_string($k)) {
                $data[$k] = $item;
            } else {
                $data[$item] = $this->alias ? $this->alias . '.' . $item : $item;
            }
        }
        return $data;
    }

    /**
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param string $op
     * @param callable|null $call
     * @return $this
     */
    protected function _set($field, string $op, callable $call = null): self
    {
        $fields = $this->_getField($field);
        foreach ($fields as $f => $d) {
            $val = $this->param[$f] ?? null;
            if (empty($val) && !is_numeric($val)) continue;

            $newOp = $op;
            if (is_callable($call)) {
                $val = $call($val, $newOp);
            }

            $this->map[] = [$d, $newOp, $val];

        }
        return $this;
    }

    /**
     * @param string|array $field 请求数据字段
     *     方式一： field1,field2,field3... 字段1,字段2...
     *     方式二： [field1=>dbField1,field2=>dbField2,field3...] 字段=>对应查询字段
     * @param bool $not 是否取反，即相反的查询操作
     * @param string $separator 分隔符
     * @param callable|null $call
     * @return $this
     */
    protected function _setBetween($field, bool $not = false, string $separator = ',', callable $call = null): self
    {
        $op = $not === true ? 'not between' : 'between';
        return $this->_set($field, $op, function ($val) use ($separator, $call) {
            $val = !is_array($val) ? explode($separator, $val) : $val;
            if (is_callable($call)) $val = $call($val);
            return $val;
        });
    }

    /**
     * 批量查询字符串转数组
     * @param array|string $str
     * @param array $replace
     * @return array
     */
    protected function batchStrToArr($str, array $replace = []): array
    {
        if (is_array($str)) return $str;

        $separator = '~#|||#~';
        $replace = !empty($replace) ? $replace : [',', '，', "\r\n", "\n\r", "\n", "\r", "\t", " "];

        $str = str_replace($replace, $separator, $str);
        $arr = array_filter(explode($separator, $str), function ($val) {
            if ($val === '0' || $val === 0 || $val === 0.0) {
                return true;
            }
            if (!empty($val)) {
                return true;
            }
            return false;
        });
        return array_values($arr);

    }
}
